using System;
using System.IO;
using System.Xml;
using System.Security.Cryptography;
using System.Windows.Forms;

namespace Magic.Core
{
    public class Cxpe
    {
        Xpe[] xpes;
        public System.Collections.ArrayList md5Name = new System.Collections.ArrayList();
        public System.Collections.ArrayList md5Hash = new System.Collections.ArrayList();
        public int RootTag;
        public Cxpe(Stream stream, Stream hash)
        {
            EndianReader er = new EndianReader(stream, ByteOrder.LittleEndian);
            xpes = new Xpe[er.ReadInt32()];

            for (int x = 0; x < xpes.Length; x++)
            {
                er.Position = 4 + (4 * x);
                er.Position = er.ReadInt32();
                xpes[x] = new Xpe(er);
            }

            md5Name.Clear();
            md5Hash.Clear();

            BinaryReader BR = new BinaryReader(hash);
            do
            {
                md5Name.Add(BR.ReadString());
                md5Hash.Add(BR.ReadBytes(16));
            } while (BR.BaseStream.Position != BR.BaseStream.Length);
            BR.Close();


            //removes Dual Extensions
            //for (int x = 0; x < md5Name.Count; x++)
            //{
            //    string hashName = md5Name[x].ToString();
            //    string[] split = null;

            //    split = hashName.Split('.');
            //    if (split.Length > 2)
            //    {
            //        if (split[split.Length - 1] == split[split.Length - 2])
            //        {
            //            md5Name[x] = hashName.Substring(0, hashName.Length - (split[split.Length - 1].Length + 1));
            //        }
            //    }
            //}

            //Removes Extension
            //for (int x = 0; x < md5Name.Count; x++)
            //{
            //    string hashName = md5Name[x].ToString();
            //    string[] split = null;

            //    split = hashName.Split('.');
            //    if (split.Length > 1)
            //    {
            //            md5Name[x] = split[0];
            //    }
            //}

            //HashFile = new FileStream("HashList.md5", FileMode.Create);
            //BinaryWriter BW = new BinaryWriter(HashFile);
            //for (int x = 0; x < md5Name.Count; x++)
            //{
            //    BW.Write(md5Name[x].ToString());
            //    BW.Write(md5Hash[x].ToString());
            //}
            //BW.Close();

        }
        public Cxpe(Stream stream)
        {
            EndianReader er = new EndianReader(stream, ByteOrder.LittleEndian);
            xpes = new Xpe[er.ReadInt32()];

            for (int x = 0; x < xpes.Length; x++)
            {
                er.Position = 4 + (4 * x);
                er.Position = er.ReadInt32();
                xpes[x] = new Xpe(er);
            }

        }

        public Cxpe(string directory)
        {
            string[] files = Directory.GetFiles(directory, "*.txt");
            xpes = new Xpe[files.Length];
            for (int x = 0; x < files.Length; x++)
                xpes[x] = new Xpe(files[x]);
        }

        public Xpe this[string type]
        {
            get
            {
                for (int x = 0; x < xpes.Length; x++)
                    if (xpes[x].FourCC == type)
                        return xpes[x];
                return null;
            }
        }

        public Xpe FromExtension(string extension)
        {
            for (int x = 0; x < xpes.Length; x++)
                if (xpes[x].Extension == extension)
                    return xpes[x];
            return null;
        }
        public Xpe FromFourCC(string fourCC)
        {
            for (int x = 0; x < xpes.Length; x++)
                if (xpes[x].FourCC == fourCC)
                    return xpes[x];
            return null;
        }
        public System.Collections.Generic.List<Xpe> InheritsFourCC(string fourCC)
        {
            System.Collections.Generic.List<Xpe> xpeList = new System.Collections.Generic.List<Xpe>();
      
            for (int x = 0; x < xpes.Length; x++)
                if ((xpes[x].FourCC == fourCC) || (xpes[x].ParentFourCC == fourCC)|| (xpes[x].GrandparentFourCC == fourCC))
                    xpeList.Add(xpes[x]);
            return xpeList;
        }
        public void Save(string filename)
        {
            EndianWriter ew = new EndianWriter(new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None), ByteOrder.LittleEndian);
            ew.Length = (xpes.Length + 1) * 4;
            ew.Position = 0;
            ew.Write(xpes.Length);
            for (int x = 0; x < xpes.Length; x++)
            {
                ew.Position = 4 + (x * 4);
                ew.Write(ew.Length);
                ew.Position = ew.Length;
                xpes[x].Write(ew);
            }
            ew.Close(); ew = null;
        }
        public void AddHash(string Name, byte[] Hash)
        {
            if (md5Name.IndexOf(Name) < 0)
            {
                md5Name.Add(Name);
                md5Hash.Add(Hash);
            }
        }
        public bool HashExists(string Name)
        {
            if (md5Name.IndexOf(Name) < 0)
                return false;
            return true;
        }
        public byte[] GetHash(string Name)
        {
            return (byte[])md5Hash[md5Name.IndexOf(Name)];

        }
        public void GetMD5Table()//string directory, string cedirectory, ToolStripStatusLabel status)
        {
            //TagLibrary tLib = new TagLibrary(this);
            //DirectoryInfo dInfo = new DirectoryInfo(directory);

            //foreach (FileInfo fInfo in dInfo.GetFiles("*.*", SearchOption.AllDirectories))
            //{

            //    string tagName = fInfo.FullName.Replace(directory + "\\", "").Replace(fInfo.Extension, "");
            //    status.Text = "Hashing '" + tagName + fInfo.Extension + '\'';
            //    //Tag nTag = tLib[tLib.GetDependencyTag(tagName, tLib.CXPE.FromExtension(fInfo.Extension.Replace(".","")).FourCC, directory, cedirectory)];
            //    Tag nTag = new Tag(fInfo.FullName, directory, tLib, cedirectory, true);
            //   // md5Name.Add(tagName);
            //    //md5Extension.Add(nTag.Xpe.Extension);
            //    //byte[] Hash = nTag.ReturnHash(tLib);
            //    //md5Hash.Add(Hash);
            //    Application.DoEvents();

            //}
            //dInfo = new DirectoryInfo(cedirectory);
            //foreach (FileInfo fInfo in dInfo.GetFiles("*.*", SearchOption.AllDirectories))
            //{
            //    string tagName = fInfo.FullName.Replace(cedirectory + "\\", "").Replace(fInfo.Extension, "");
            //    status.Text = "Hashing '" + tagName + '\'';
            //    Tag nTag = new Tag(fInfo.FullName, cedirectory, tLib, cedirectory, true);
            //    md5Name.Add(tagName);
            //    md5Hash.Add(nTag.ReturnHash(tLib));
            //    Application.DoEvents();
            //}
            FileStream HashFile = new FileStream("HashList.md5", FileMode.Create);
            BinaryWriter BW = new BinaryWriter(HashFile);
            for (int x = 0; x < md5Name.Count; x++)
            {
                BW.Write(md5Name[x].ToString());
                BW.Write((byte[])md5Hash[x]);
            }
            BW.Close();
            HashFile.Close();
            // status.Text = "Done.";
        }
        //public static string GetHash(byte[] Hash)
        //{

        //    string result = string.Empty;
        //    for (int x = 0; x < 16; x++)
        //    {
        //        result += Hash[x].ToString("X");
        //    }
        //    return result;
        //}
        public void Save()
        {
            for (int x = 0; x < xpes.Length; x++)
            {
                XmlTextWriter xWriter = new XmlTextWriter(@"XPE\" + xpes[x].FourCC + ".xml", System.Text.Encoding.UTF8);
                xWriter.WriteStartDocument();
                xWriter.WriteStartElement(xpes[x].Extension);
                xWriter.WriteAttributeString("HeaderLength", xpes[x].HeaderLength.ToString());
                xWriter.WriteAttributeString("FourCC", xpes[x].FourCC);
                xWriter.WriteAttributeString("ParentFourCC", xpes[x].ParentFourCC);
                xWriter.WriteAttributeString("GrandparentFourCC", xpes[x].GrandparentFourCC);
                xWriter.WriteAttributeString("Version", xpes[x].Version.ToString());
                WriteBlocks(xWriter, xpes[x].Children);
                xWriter.WriteEndElement();
                xWriter.WriteEndDocument();
                xWriter.Close();

            }
        }
        public void CreateVB()
        {
            for (int x = 0; x < xpes.Length; x++)
            {
                string folders = "";
                if (xpes[x].GrandparentFourCC != "????")
                    folders += FromFourCC(xpes[x].GrandparentFourCC).Extension.ToLower() + "\\";
                if (xpes[x].ParentFourCC != "????")
                    folders += FromFourCC(xpes[x].ParentFourCC).Extension.ToLower() + "\\";
                DirectoryInfo dInfo = new DirectoryInfo(@"..\..\Tags\" + folders);
                if (!dInfo.Exists)
                    dInfo.Create();
                StreamWriter xWriter = new StreamWriter(@"..\..\Tags\" + folders + xpes[x].Extension + ".vb");
                xWriter.WriteLine("<TagAttribute({0}{1}{0},{0}{2}{0},{0}{3}{0},{0}{4}{0},{5},{6})>",new object[] { "\"", xpes[x].Extension, xpes[x].FourCC, xpes[x].ParentFourCC, xpes[x].GrandparentFourCC, xpes[x].Version, xpes[x].HeaderLength });
                xWriter.WriteLine("Public Class " + xpes[x].Extension.ToUpper() + "_TAG");
                xWriter.WriteLine("Inherits HaloTag");

                xWriter.WriteLine("Public Const Extension as String = \"" + xpes[x].Extension + "\"");
                xWriter.WriteLine("Public Const FourCC as String = \"" + xpes[x].FourCC + "\"");
                xWriter.WriteLine("Public Const ParentFourCC as String = \"" + xpes[x].ParentFourCC + "\"");
                xWriter.WriteLine("Public Const GrandParentFourCC as String = \"" + xpes[x].GrandparentFourCC + "\"");
                xWriter.WriteLine("Public Const [Version] as Short = " + xpes[x].Version);
                xWriter.WriteLine("Public Const HeaderLength as Integer = " + xpes[x].HeaderLength);

                xWriter.WriteLine(WriteBlocksVB(xpes[x].Children));

                xWriter.WriteLine("End Class");
                //xWriter.WriteLine("End Namespace");
                xWriter.Close();

            }
        }
        public string WriteBlocksVB(Xpe.Field[] Children, int startindex = 0, bool inherited = false, bool block = false)
        {
            //try
            //{
            string declarationString = "";
            string subclassString = "";

            char LineFeed = (char)0x0d;
            char NewLine = (char)0x0a;
            bool regionStarted = false;

            string NextLine = LineFeed.ToString() + NewLine.ToString();


            //readString += "Parent.ReadTag(eR)" + NextLine;
            //writeString += "Parent.WriteTag(eR)" + NextLine;

            System.Collections.ArrayList names = new System.Collections.ArrayList();

            for (int x = startindex; x < Children.Length; x++)
            {
                string name = Children[x].Name;
                name = name.Replace("'", "");
                name = name.Replace("/", "");
                name = name.Replace("<", "");
                name = name.Replace(">", "");
                name = name.Replace("\"", "");
                name = name.Replace("+", "_");
                if (name.Contains(":")) { name = name.Remove(name.IndexOf(":")); }
                if (name.Contains("-")) { name = name.Remove(name.IndexOf("-")); }
                if (name.Contains("[")) { name = name.Remove(name.IndexOf("[")); }
                if (name.Contains("$")) { name = name.Remove(name.IndexOf("$")); }
                if (name.Contains(".")) { name = name.Remove(name.IndexOf(".")); }
                if (name.Contains("#")) { name = name.Remove(name.IndexOf("#")); }
                if (name.Contains("^")) { name = name.Remove(name.IndexOf("^")); }
                if (name.Contains("*")) { name = name.Remove(name.IndexOf("*")); }
                if (name.Contains("(")) { name = name.Remove(name.IndexOf("(")); }
                if (name.Contains(")")) { name = name.Remove(name.IndexOf(")")); }
                name = name.Trim().Replace(" ", "_");
                if (name == "")
                    name = (Children[x].Type ==  Xpe.FieldType.Padding) ? "padding" : "unknown";

                string[] keywords = new string[] { "function", "class", "new", "on", "add", "end", "loop", "and", "string" };
                if ((Array.IndexOf(keywords, name) > -1) || char.IsDigit((char)name.ToCharArray().GetValue(0)))
                {
                    name = "_" + name;
                }

                if (names.Contains(name))
                {
                    for (int names_x = 0; names_x < names.Count; names_x++)
                    {
                        string temp_name = name + "_" + (names_x + 1).ToString();
                        if (!names.Contains(temp_name))
                        {
                            name = temp_name;
                            break;
                        }
                    }
                }
                names.Add(name);


                declarationString += NextLine;

                declarationString += "''' <summary>" + NextLine;
                foreach (string s in Children[x].Description.Split(Environment.NewLine.ToCharArray()))
                {
                    declarationString += String.Format("''' {0}", s) + NextLine;
                }
                declarationString += "''' </summary>" + NextLine;

                switch (Children[x].Type)
                {
                    case Xpe.FieldType.String:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name.Replace("\"", ""), Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as String" + NextLine;
                        break;
                    case Xpe.FieldType.Float1:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name.Replace("\"", ""), Children[x].Size/4, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Single" + NextLine;
                        break;
                    case Xpe.FieldType.Float2:
                    case Xpe.FieldType.Float3:
                    case Xpe.FieldType.Float4:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size / 4, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Single()" + NextLine;
                        break;
                    case Xpe.FieldType.DWordFlags:
                    case Xpe.FieldType.Int1:
                    case Xpe.FieldType.SelfReference:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size / 4, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Integer" + NextLine;
                        break;
                    case Xpe.FieldType.Int2:
                    case Xpe.FieldType.Int3:
                    case Xpe.FieldType.Int4:
                    case Xpe.FieldType.ModelIndexBlock:
                    case Xpe.FieldType.ModelVertexBlock:
                    case Xpe.FieldType.BitmapDataBlock:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size / 4, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Integer()" + NextLine;
                        break;
                    case Xpe.FieldType.WordFlags:
                    case Xpe.FieldType.Enum:
                    case Xpe.FieldType.Short1:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size / 2, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Short" + NextLine;
                        break;
                    case Xpe.FieldType.Short2:
                    case Xpe.FieldType.Short3:
                    case Xpe.FieldType.Short4:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size / 2, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Short()" + NextLine;
                        break;
                    case Xpe.FieldType.Char:
                    case Xpe.FieldType.CharFlags:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Byte" + NextLine;
                        break;

                    case Xpe.FieldType.Padding:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as Byte()" + NextLine;
                        break;
                    case Xpe.FieldType.Block:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name.ToLower() + " as List(Of " + name.ToUpper() + "_BLOCK)" + NextLine;
                        subclassString += "Public Class " + name.ToUpper() + "_BLOCK" + NextLine;
                        subclassString += WriteBlocksVB(Children[x].Children, 0, false, true) + NextLine;
                        subclassString += "End Class" + NextLine;
                        break;

                    case Xpe.FieldType.Prestructure:
                    case Xpe.FieldType.ScriptData:
                    case Xpe.FieldType.BspVertexDataHeader:
                    case Xpe.FieldType.BspCompressedVertexBlock:
                    case Xpe.FieldType.BspUncompressedVertexBlock:
                    case Xpe.FieldType.BitmapPixelData:
                    case Xpe.FieldType.SoundDataBlock:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name + " as New DATA_CONTAINER" + NextLine;
                        break;

                    case Xpe.FieldType.TagReference:
                        declarationString += string.Format("<TagField({0}{1}{0}, {2}, {3})>", "\"", Children[x].Name, Children[x].Size, Children[x].Type.ToString("d")) + NextLine;
                        declarationString += "Public " + name.ToLower() + " as New TAG_REFERENCE" + NextLine;
                        break;

                    case Xpe.FieldType.Explanation:
                        if (regionStarted)
                            declarationString += "#End Region" + NextLine;
                        declarationString += "#Region \"" + Children[x].Name + "\"" + NextLine;
                        regionStarted = true;
                        break;
                    default:
                        declarationString += String.Format("'TODO: Name:{0}, Size:{1}, Type:{2}", name, Children[x].Size.ToString(), Children[x].Type.ToString()) + NextLine;
                        Console.WriteLine(String.Format("'TODO: Name:{0}, Size:{1}, Type:{2}", name, Children[x].Size.ToString(), Children[x].Type.ToString()));
                        break;
                }
            }
            if (regionStarted)
                declarationString += "#End Region" + NextLine;
            

            return declarationString + subclassString;
        }
        public void WriteBlocks(XmlTextWriter xW, Xpe.Field[] Children)
        {
            //try
            //{
            for (int x = 0; x < Children.Length; x++)
            {
                xW.WriteStartElement(Children[x].Type.ToString());
                xW.WriteAttributeString("name", Children[x].Name);
                xW.WriteAttributeString("description", Children[x].Description);
                xW.WriteAttributeString("offset", Children[x].Offset.ToString());
                xW.WriteAttributeString("size", Children[x].Size.ToString());
                xW.WriteAttributeString("maxelements", Children[x].MaxElementCount.ToString());
                xW.WriteAttributeString("index", x.ToString());
                switch (Children[x].Type)
                {
                    case Xpe.FieldType.Block:
                        WriteBlocks(xW, Children[x].Children);
                        break;
                    case Xpe.FieldType.Enum:
                        for (int a = 0; a < Children[x].Arguments.Length; a++)
                        {
                            xW.WriteStartElement("option");
                            xW.WriteAttributeString("name", Children[x].Arguments[a]);
                            xW.WriteAttributeString("value", a.ToString());
                            xW.WriteEndElement();
                        }
                        break;
                    case Xpe.FieldType.CharFlags:
                    case Xpe.FieldType.WordFlags:
                    case Xpe.FieldType.DWordFlags:
                        for (int a = 0; a < Children[x].Arguments.Length; a++)
                        {
                            xW.WriteStartElement("bit");
                            xW.WriteAttributeString("name", Children[x].Arguments[a]);
                            xW.WriteAttributeString("value", (a).ToString());
                            xW.WriteEndElement();
                        }
                        break;
                }
                xW.WriteEndElement();
            }
            //}
            //catch
            //{
            //    xW.Flush();
            //}
        }
    }
}